---
title: 线程池
description: 如何创建正确的线程池？
---
虽然在 Java 语言中创建线程看上去就像创建一个对象一样简单，只需要 new Thread() 就可以了，但实际上创建线程远不是创建一个对象那么简单。创建对象，仅仅是在 JVM 的堆里分配一块内存而已；
而创建一个线程，却需要调用操作系统内核 API，然后操作系统要为线程分配一系列资源，这个成本就很高了，所以**线程是一个重量级的对象，应该避免频繁的创建和销毁。**

如何避免呢？就是通过线程池。

:::tip
线程池和一般意义上的池化资源是不同的。一般意义上的池化资源，都是下面这样，当你需要资源的时候就调用 acquire() 方法来申请资源，用完之后就调用 release() 释放资源。

```java
class XXXPool{
  // 获取池化资源
  XXX acquire() {
  }
  // 释放池化资源
  void release(XXX x){
  }
}
```

Java 提供的线程池里面压根没有申请线程和释放线程的方法。
:::
## 线程池是一种生产者—消费者模式
```java title='为什么线程池没有采用一般意义上池化资源的设计方法呢？如果线程池采用一般意义上池化资源的设计方法，应该是下面示例代码这样。假设我们获取到一个空闲线程 T1，然后该如何使用 T1 呢？你期望的可能是这样：通过调用 T1 的 execute() 方法，传入一个 Runnable 对象来执行具体业务逻辑，就像通过构造函数 Thread(Runnable target) 创建线程一样。可惜的是，你翻遍 Thread 对象的所有方法，都不存在类似 execute(Runnable target) 这样的公共方法。'
//采用一般意义上池化资源的设计方法
class ThreadPool{
  // 获取空闲线程
  Thread acquire() {
  }
  // 释放线程
  void release(Thread t){
  }
}
//期望的使用
ThreadPool pool；
Thread T1=pool.acquire();
//传入Runnable对象
T1.execute(()->{
  //具体业务逻辑
  ......
});
```
所以，线程池的设计，没有办法直接采用一般意义上的池化资源的设计方法。

目前业界线程池的设计，普遍采用的都是 生产者——消费者模式。线程池的使用方是生产者，线程池本身是消费者。

```java title='创建一个简化版的线程池，仅用来说明工作原理'
package pub.wkq.thread;


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;

/**
 * 简化的线程池，仅用来说明工作原理
 * @author 王开琦
 */
class MyThreadPool {
    /**
     * 利用阻塞队列实现生产者——消费者模式
     */
    private BlockingQueue<Runnable> workQueue;
    /**
     * 保存内部工作线程
     */
    private List<WorkerThread> threads = new ArrayList<>();

    MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue) {
        this.workQueue = workQueue;
        //创建工作线程
        for (int idx = 0; idx < poolSize; idx++) {
            WorkerThread thread = new WorkerThread();
            thread.start();
            threads.add(thread);
        }
    }

    //提交任务
   public void execute(Runnable command) {
        workQueue.add(command);
    }

    /**
     * 工作线程负责消费任务，并执行任务
     */
    class WorkerThread extends Thread {
        @Override
        public void run() {
            while (true) {
                Runnable task = null;
                try {
                    task = workQueue.take();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                task.run();
            }
        }
    }
}
```
```java title='测试代码'
package pub.wkq.thread;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class Test {
    public static void main(String[] args) {
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10);
        MyThreadPool pool = new MyThreadPool(10, workQueue);
        for (int i = 0; i < 10; i++) {
            pool.execute(()->{
                System.out.println("hello"+Thread.currentThread().getName());
            });
        }
        System.out.println("pool = " + pool);
    }
}
```

在 MyThreadPool 的内部，维护了一个阻塞队列 workQueue 和一组工作线程，工作线程的个数由构造函数中的 poolSize 指定，。用户通过 execute() 方法来提交 Runnable 任务，execute() 方法的内部实现仅仅是将任务加入到 workQueue 中。MyThreadPool 内部维护的工作线程会消费 workQueue 中的任务并执行任务。

## Java 中的线程池
Java 提供的线程池相关的工具类中，最核心的是 **ThreadPoolExecutor**。它的构造函数非常复杂，最完备的构造函数有 7 个参数:
```java
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
    ...
}
```
* corePoolSize: 表示线程保有的最小线程数。

* maximumPoolSize：表示线程池创建的最大线程数。当线程空闲时，非 corePoolSize 线程会销毁。

* keepAliveTime & unit: 一个线程如果在一段时间内，都没有执行任务，这个空闲的线程就要被回收了。使用 keepAliveTime & unit 来定义这个一段时间。

* workQueue：工作队列

* threadFactory: 通过这个参数可以自定义如何创建线程，例如给线程指定一个有意义的名字。

* handler：通过这个参数可以自定义任务的拒绝策略。例如线程池中所有的线程都在忙碌，并且工作队列也满了（工作队列使用有界队列），那么此时提交任务，线程池就会拒绝接受。至于拒绝的策略，可以通过 handler 这个参数来指定。ThreadPoolExecutor 已经提供了以下四种策略。

  

  * CallerRunsPolicy：提交任务的线程自己去执行该任务。
  * AbortPolicy：默认的拒绝策略，会 throws RejectedExecutionException。
  * DiscardPolicy: 直接丢弃任务，没有任何异常抛出。
  * DiscardOldestPolicy: 丢弃最老的任务，其实就是把进入工作队列最早的任务丢弃，然后把新任务加入到工作队列。

:::tip

Java 在 1.6 版本还增加了 `allowCoreThreadTimeOut(boolean value)` 方法，它可以让所有的线程都支持超时。但最少要保留一个工作线程。

:::

###  线程池有那些 workQueue

- 直接提交队列（SynchronousQueue）：不存储任务，直接将任务交给线程执行，如果没有空闲线程，则创建新的线程
- 有界任务队列（ArrayBlockingQueue）：采用数组实现的阻塞队列，需要指定容量，如果队列满了，则拒绝新的任务
- 无界任务队列（LinkedBlockingQueue）：采用链表实现的阻塞队列，可以存储无限数量的任务，但可能导致内存溢出或者执行效率低下
- 优先任务队列（PriorityBlockingQueue）：采用堆结构实现的阻塞队列，可以按照优先级顺序执行任务，但需要实现 Comparable 接口。

### 自定义 ThreadFactory

自定义线程工厂是一种可以指定线程名称、优先级、是否为守护线程等属性的工厂类，它实现了 ThreadFactory 接口，并重写了 newThread 方法。自定义线程工厂可以在创建线程池时传入，以便在多线程场景下方便问题的定位和排查

```java
class MyThreadFactory implements ThreadFactory {
    private final String namePrefix;
    private final AtomicInteger nextId = new AtomicInteger(1);

    /**
     * 定义线程名称，在使用 jstack 来排查问题时，非常有帮助
     *
     * @param whatFeatureOfGroup
     */
    public MyThreadFactory(String whatFeatureOfGroup) {
        this.namePrefix = "From myThreadFactory: " + whatFeatureOfGroup + "-worker-";
    }

    @Override
    public Thread newThread(Runnable task) {
        String name = namePrefix + nextId.getAndIncrement();
        Thread thread = new Thread(null, task, name, 0);
        return thread;
    }
}
```



## 使用线程池注意事项

不建议使用 Executors ,**强烈建议使用有界队列**。



使用有界队列，任务过多时，线程池会触发执行拒绝策略，线程池默认的拒绝策略会 throw RejectExecutionException 这个运行时异常，对于运行时异常编译器并不强制 catch 它，所以开发人员很容易忽略。因此**默认拒绝策略要慎用**。



如果线程池处理的任务非常重要，建议自定义自己的拒绝策略；并且在实际工作中，自定义的拒绝策略往往和降级策略配合使用。



使用线程池，还需要注意异常处理的问题，例如通过 ThreadPoolExecutor 对象的 executor() 方法提交任务时，虽然任务在执行的过程中出现运行时异常，会导致执行任务的线程终止；不过，但你却获取不到任何通知，这会让你误以为任务 执行的都很正常。虽然线程池提供了 很多用于异常处理的方法，但是最稳妥和简单的方案还是捕获所有异常并按需处理。

```java

try {
  //业务逻辑
} catch (RuntimeException x) {
  //按需处理
} catch (Throwable x) {
  //按需处理
} 
```



## 总结

线程池和普通的池化资源有很大不同，线程池实际上是生产者——消费者模式的一种实现。

